﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using K9Nano.Application;
using K9Nano.Application.Extensions;
using K9Nano.Authentication;
using K9Nano.Authorization;
using K9Nano.Dependency;
using K9Nano.Domain;
using K9Nano.Runtime;
using K9Nano.Share.Extensions;
using K9Nano.Web.Accounts.Dto;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;

namespace K9Nano.Web.Accounts
{
    public class RoleCurdAppService : K9ApplicationService, IRoleCurdAppService, IScopedDependency
    {
        protected readonly RoleManager<Role> RoleManager;
        protected readonly IRbacManager RbacManager;

        public RoleCurdAppService(IUnitOfWork uow, IMapper mapper, ISessionContext sessionContext, ILoggerFactory loggerFactory,
            RoleManager<Role> roleManager, IRbacManager rbacManager) : base(uow, mapper, sessionContext, loggerFactory)
        {
            RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
            RbacManager = rbacManager;
        }

        public virtual async Task<RoleItemDto> CreateAsync(RoleCreateDto dto, CancellationToken cancellation = default)
        {
            var role = Mapper.Map<Role>(dto);
            var result = await RoleManager.CreateAsync(role);
            if (result.Succeeded)
            {
                return Mapper.Map<RoleItemDto>(role);
            }

            throw result.ToException();
        }

        public virtual async Task<RoleItemDto> UpdateAsync(RoleCreateDto dto, CancellationToken cancellation = default)
        {
            var role = Mapper.Map<Role>(dto);
            var result = await RoleManager.UpdateAsync(role);
            if (result.Succeeded)
            {
                return Mapper.Map<RoleItemDto>(role);
            }

            throw result.ToException();
        }

        public virtual async Task<PagedResultDto<RoleItemDto>> ListAsync(RolePagedInputDto input, CancellationToken cancellation = default)
        {
            int count = 0;
            List<RoleItemDto> items = null;

            if (!string.IsNullOrWhiteSpace(input.Resource))
            {
                var resourceRep = Uow.Repository<RoleResource>();

                Expression<Func<RoleResource, bool>> query = r => r.Api == input.Api;
                if (!string.IsNullOrWhiteSpace(input.Role))
                {
                    query = ExpressionBuilder.Combine(query, x => x.Role == input.Role);
                }

                count = await resourceRep.CountAsync(query, cancellation);
                if (count > 0)
                {
                    var result = await resourceRep.GetPagedListAsync(query, x => x.Role, input.Skip(), input.PageSize,
                            cancellation);

                    items = new List<RoleItemDto>(result.Count);
                    foreach (var roleResource in result)
                    {
                        var role = await RoleManager.FindByNameAsync(roleResource.Role);

                        var dto = new RoleItemDto
                        {
                            Id = role.Id,
                            CreationTime = role.CreationTime,
                            Name = roleResource.Role,
                            Api = roleResource.Api
                        };

                        items.Add(dto);
                    }
                }
                else
                {
                    items = new List<RoleItemDto>(0);
                }
            }
            else
            {
                var repository = Uow.Repository<Role, Guid>();
                Expression<Func<Role, bool>> query = r => r.Api == input.Api;
                if (!string.IsNullOrWhiteSpace(input.Role))
                {
                    query = ExpressionBuilder.Combine(query, x => x.Name == input.Role);
                }
                count = await repository.CountAsync(query, cancellation);
                if (count > 0)
                {
                    var result =
                        await repository.GetPagedListAsync(query, x => x.Name, input.Skip(), input.PageSize,
                            cancellation);
                    items = Mapper.Map<List<RoleItemDto>>(result);
                }
                else
                {
                    items = new List<RoleItemDto>(0);
                }
            }

            return new PagedResultDto<RoleItemDto>(count, items);
        }

        public virtual async Task DeleteAsync(Guid id, CancellationToken cancellation = default)
        {
            var role = await Uow.Repository<Role, Guid>().GetAsync(id, cancellation);
            if (role != null)
            {
                await RoleManager.DeleteAsync(role);
            }
        }

        public virtual async Task<IReadOnlyList<RoleItemDto>> ListAsync(string api, string[] roles, CancellationToken cancellation = default)
        {
            var repository = Uow.Repository<Role, Guid>();

            var result = await repository.GetAllListAsync(r => r.Api == api && roles.Contains(r.Name), cancellation);
            var list = Mapper.Map<List<RoleItemDto>>(result);
            return list;
        }

        public virtual async ValueTask<IReadOnlyList<string>> GetRolesInResourceAsync(string api, string resource, CancellationToken cancellation)
        {
            var result = await RbacManager.GetRolesInResourceAsync(api, resource, cancellation);
            return result;
        }

        public virtual async Task<RoleItemDto> GetAsync(Guid id, CancellationToken cancellation = default)
        {
            var repository = Uow.Repository<Role, Guid>();
            var role = await repository.GetAsync(id, cancellation);
            if (role == null) return null;
            return Mapper.Map<RoleItemDto>(role);
        }
    }
}